﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using GameLibrary;
using GameLibrary.Settlement;
using GameLibrary.Unit;
using GameLibrary.Interfaces;

namespace UserInterface.Presenters
{
    [Serializable]
    internal class FinitStateMachine
    {
        #region variables

        private UnitInfo CurrentUnit;

        private SettlementInfo CurrentSettlement;
        private SettlementInfo NewSettlement;

        private CommandType Command;

        private Position NewPosition;

        private Template Template;

        private GameInterfaceCoordinator Coordinator;


        #endregion

        public FinitStateMachine(GameInterfaceCoordinator coordinator)
        {
            this.Coordinator = coordinator;
            this.InitState();
        }

        #region init state

        private void InitState()
        {
            this.Command = CommandType.None;
            this.CurrentUnit = null;
            this.NewPosition = Position.MinusOne;
            this.NewSettlement = null;
            this.CurrentSettlement = null;
            this.Template = null;
        }

        private void Reset()
        {
            this.InitState();
            this.Coordinator.Reset();
        }

        #endregion

        #region input

        internal void Input(
            IInfo info = null, 
            CommandType command = CommandType.None, 
            Position? position = null,
            Template template = null)
        {
            if (IsStateValidForInitOr1stInput)
                this.ChangeState(info, command, position, template);
            else
                this.Reset();

            if (IsValidFor2ndInput)
                this.MakeAction();
            else
                if (!IsStateValidForInitOr1stInput)
                    this.Reset();
        }

        #endregion

        #region check valid

        private bool IsStateValidForInitOr1stInput
        {
            get
            {
                return IsAllNull
                    || IsValidFor1stInput;
            }
        }

        private bool IsAllNull
        {
            get
            {
                return this.IsBothNull(this.CurrentSettlement, this.NewSettlement)
                    && this.IsNull(this.CurrentUnit)
                    && this.IsNull(this.NewPosition)
                    && this.IsNull(this.Command);
            }
        }

        private bool IsValidFor1stInput
        {
            get
            {
                return IsValidFor1stAttackInput
                    || IsValidFor1stExplore
                    || IsValidFor1stLintToCity
                    || IsValidFor1stCityBuildUnitInput
                    || IsValidFor1stUnitUpgrade
                    || IsValidFor1stMoveInput;
            }
        }

        private bool IsValidFor2ndInput
        {
            get
            {
                return IsValidFor2ndAttackInput
                    || IsValidFor2ndExplore
                    || IsValidFor2ndLinkToCity
                    || IsValidFor2ndCityBuildUnitInput
                    || IsValidFor2ndUnitUpgrade
                    || IsValidFor2ndMoveInput;
            }
        }

        private bool IsValidFor1stUnitUpgrade
        {
            get
            {
                return IsBothNull(this.CurrentSettlement, this.NewSettlement)
                    && IsNull(this.CurrentUnit) == false
                    && IsNull(this.NewPosition)
                    && IsNull(this.Template)
                    && IsUnitUpgrade(this.Command);
            }
        }

        private bool IsUnitUpgrade(CommandType command)
        {
            return command == CommandType.UnitUpgrade;
        }

        private bool IsValidFor2ndUnitUpgrade
        {
            get
            {
                return IsBothNull(this.CurrentSettlement, this.NewSettlement)
                    && IsNull(this.CurrentUnit) == false
                    && IsNull(this.Template) == false
                    && IsNull(this.NewPosition)
                    && IsUnitUpgrade(this.Command);
            }
        }

        private bool IsValidFor1stCityBuildUnitInput
        {
            get
            {
                return IsNull(this.CurrentSettlement) == false
                    && IsNull(this.NewSettlement)
                    && IsNull(this.CurrentUnit)
                    && IsNull(this.NewPosition)
                    && IsNull(this.Template)
                    && IsCityBuildUnit(this.Command);
            }
        }

        private bool IsCityBuildUnit(CommandType comand)
        {
            return Command == CommandType.CityBuildUnit;
        }

        private bool IsValidFor2ndCityBuildUnitInput
        {
            get
            {
                return IsNull(this.CurrentSettlement) == false
                    && IsNull(this.NewSettlement)
                    && IsNull(this.CurrentUnit)
                    && IsNull(this.NewPosition)
                    && IsNull(this.Template) == false
                    && IsCityBuildUnit(this.Command);
            }
        }   

        private bool IsValidFor1stAttackInput
        {
            get
            {
                return IsBothNull(this.CurrentSettlement, this.NewSettlement)
                    && IsUnitInfo(this.CurrentUnit)
                    && IsNull(this.NewPosition)
                    && IsAttack(this.Command);
            }
        }

        private bool IsValidFor1stMoveInput
        {
            get
            {
                return IsBothNull(this.CurrentSettlement, this.NewSettlement)
                    && IsUnitInfo(this.CurrentUnit)
                    && IsNull(this.NewPosition)
                    && IsMove(this.Command);
            }
        }

        private bool IsValidFor1stExplore
        {
            get
            {
                return IsBothNull(this.CurrentSettlement, this.NewSettlement)
                    && IsUnitInfo(this.CurrentUnit)
                    && IsNull(this.NewPosition)
                    && IsExplore(this.Command);
            }
        }

        private bool IsValidFor1stLintToCity
        {
            get
            {
                return IsSettlementInfo(this.CurrentSettlement)
                    && IsNull(this.NewSettlement)
                    && IsNull(this.CurrentUnit)
                    && IsNull(this.NewPosition)
                    && IsLinkToCity(this.Command);
            }
        }

        private bool IsValidFor2ndAttackInput
        {
            get
            {
                return IsBothNull(this.CurrentSettlement, this.NewSettlement)
                    && IsUnitInfo(this.CurrentUnit)
                    && IsInRange(this.CurrentUnit, this.NewPosition, this.Command)
                    && IsAttack(this.Command);
            }
        }

        private bool IsValidFor2ndMoveInput
        {
            get
            {
                return IsBothNull(this.CurrentSettlement, this.NewSettlement)
                    && IsUnitInfo(this.CurrentUnit)
                    && IsInRange(this.CurrentUnit, this.NewPosition, this.Command)
                    && IsMove(this.Command);
            }
        }

        private bool IsValidFor2ndExplore
        {
            get
            {
                return IsBothNull(this.CurrentSettlement, this.NewSettlement)
                    && IsUnitInfo(this.CurrentUnit)
                    && IsInRange(this.CurrentUnit, this.NewPosition, this.Command)
                    && IsExplore(this.Command);
            }
        }

        private bool IsValidFor2ndLinkToCity
        {
            get
            {
                return IsSettlementInfo(this.CurrentSettlement)
                    && IsSettlementInfo(this.NewSettlement)
                    && IsNull(this.CurrentUnit)
                    && IsNull(this.NewPosition)
                    && IsOneVillageAndOneCity(this.CurrentSettlement, this.NewSettlement)
                    && IsLinkToCity(this.Command);
            }
        }

        private bool IsBothNull(IInfo infoA, IInfo infoB)
        {
            return this.IsNull(infoA)
                && this.IsNull(infoB);
        }

        /// <summary>
        /// Test object is null or not.
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private bool IsNull(object info)
        {
            return info == null;
        }

        /// <summary>
        /// Test position == (-1,-1) -- which is initial value in fsm
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        private bool IsNull(Position pos)
        {
            return pos == Position.MinusOne;
        }

        /// <summary>
        /// Test command is command.None or not
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        private bool IsNull(CommandType command)
        {
            return command == CommandType.None;
        }

        private bool IsUnitInfo(IInfo info)
        {
            return info is UnitInfo;
        }

        private bool IsSettlementInfo(IInfo info)
        {
            return info is SettlementInfo;
        }

        private bool IsOneVillageAndOneCity(SettlementInfo settlementA, SettlementInfo settlementB)
        {
            return (IsCity(settlementA) && IsVillage(settlementB))
                || (IsVillage(settlementA) && IsCity(settlementB));
        }

        private bool IsVillage(SettlementInfo settlement)
        {
            return settlement.Type == SettlementFlags.Village;
        }

        private bool IsCity(SettlementInfo settlement)
        {
            return settlement.Type == SettlementFlags.City;
        }

        private bool IsAttack(CommandType command)
        {
            return command == CommandType.UnitAttack;
        }

        private bool IsExplore(CommandType command)
        {
            return command == CommandType.UnitExplore;
        }

        private bool IsMove(CommandType command)
        {
            return command == CommandType.UnitMove;
        }

        private bool IsLinkToCity(CommandType command)
        {
            return command == CommandType.SettlementLink;
        }

        private bool IsInRange(UnitInfo unit, Position pos, CommandType command)
        {
            var ValidRange = this.GetRange(unit,command);
            return ValidRange.Contains(pos);
        }

        /// <summary>
        /// return if command not attack, explore, move,
        /// </summary>
        /// <param name="unit"></param>
        /// <param name="command"></param>
        /// <returns></returns>
        private IEnumerable<Position> GetRange(UnitInfo unit, CommandType command)
        {
            switch (command)
            {
                case CommandType.UnitAttack:
                    return Coordinator.GetValidUnitAttackRegionChangedToGameMapDimension(unit);
                case CommandType.UnitExplore:
                    return Coordinator.GetValidUnitExploreRegionChangedToGameMapDimension(unit);
                case CommandType.UnitMove:
                    return Coordinator.GetValidUnitMoveRegionChangedToGameMapDimension(unit);
                default :
                    return null;
            }
        }

        #endregion

        #region change state

        private void ChangeState(IInfo info, 
                                 CommandType command, 
                                 Position? position, 
                                 Template template)
        {
            this.ChangeInfoValue(info);
            this.ChangeCommandValue(command);
            this.ChangePosition(position);
            this.ChangeTemplate(template);
        }

        private void ChangeTemplate(Template template)
        {
            this.Template = template;
        }

        private void ChangeInfoValue(IInfo info)
        {
            if (IsNull(info))
                return;

            if (info is UnitInfo)
                if (IsNull(this.CurrentUnit))
                    this.CurrentUnit = info as UnitInfo;
                else
                    return;
            else
                if (IsNull(this.CurrentSettlement))
                    this.CurrentSettlement = info as SettlementInfo;
                else
                    this.NewSettlement = info as SettlementInfo;
        }

        private void ChangeCommandValue(CommandType command)
        {
            if (
         //       this.Command == CommandType.None
         //    && 
             command != CommandType.None
                )
                this.Command = command;
        }

        private void ChangePosition(Position? position)
        {
            if (position.HasValue)
                this.NewPosition = position.Value;
        }

        #endregion

        #region action

        private void MakeAction()
        {
            switch (this.Command)
            {
                case CommandType.SettlementLink:
                    this.LinkToCity();
                    break;
                case CommandType.UnitAttack:
                    this.Attack();
                    break;
                case CommandType.UnitExplore:
                    this.Explore();
                    break;
                case CommandType.UnitMove:
                    this.Move();
                    break;
                case CommandType.CityBuildUnit:
                    this.CityBuildUnit();
                    break;
                case CommandType.UnitUpgrade:
                    this.UnitUpgrade();
                    break;
            }

            this.Reset();
        }

        private void Move()
        {
            Coordinator.UnitMove(this.CurrentUnit, this.NewPosition);
        }

        private void Attack()
        {
            Coordinator.UnitAttack(this.CurrentUnit, this.NewPosition);
        }

        private void Explore()
        {
            Coordinator.UnitExplore(this.CurrentUnit, this.NewPosition);
        }

        private void LinkToCity()
        {
            Coordinator.LinkToCity(this.CurrentSettlement, this.NewSettlement);
        }

        private void UnitUpgrade()
        {
            Coordinator.UnitUpgrad(this.CurrentUnit, this.Template);
        }
        private void CityBuildUnit()
        {
            Coordinator.CityBuildUnit(this.CurrentSettlement, this.Template);
        }

        #endregion

    }
}
